package org.kele.sc2020.consumer.config;

import io.github.resilience4j.bulkhead.BulkheadConfig;
import io.github.resilience4j.bulkhead.ThreadPoolBulkheadConfig;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.core.ContextPropagator;
import io.github.resilience4j.timelimiter.TimeLimiterConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JCircuitBreakerFactory;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JConfigBuilder;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4jBulkheadConfigurationBuilder;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4jBulkheadProvider;
import org.springframework.cloud.client.circuitbreaker.Customizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import java.time.Duration;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Supplier;

@Configuration
public class BizFeignConfig {


    @Bean
    public Customizer<Resilience4JCircuitBreakerFactory> resilience4JCircuitBreakerFactoryCustomizer() {
        return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
                .timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(10)).build())
                .circuitBreakerConfig(CircuitBreakerConfig.ofDefaults())
                .build());
    }

    @Bean
    public Customizer<Resilience4jBulkheadProvider> resilience4jBulkheadProviderCustomizer() {
        return provider -> provider.configureDefault(id -> new Resilience4jBulkheadConfigurationBuilder()
                .bulkheadConfig(BulkheadConfig.ofDefaults())
                .threadPoolBulkheadConfig(ThreadPoolBulkheadConfig
                        .custom()
                        .contextPropagator(CustomInheritContextPropagator.getInstance()) // 传递threadlocal;
                        .build())
                .build()
        );
    }


    private static class CustomInheritContextPropagator implements ContextPropagator<RequestAttributes> {

        private static final CustomInheritContextPropagator CUSTOM_INHERIT_CONTEXT_PROPAGATOR = new CustomInheritContextPropagator();

        public static CustomInheritContextPropagator getInstance() {
            return CUSTOM_INHERIT_CONTEXT_PROPAGATOR;
        }

        @Override
        public Supplier<Optional<RequestAttributes>> retrieve() {
            //  give requestcontext to reference from threadlocal;
            //  this method call by web-container thread, such as tomcat, jetty,or undertow, depends on what you used;
            return () -> Optional.ofNullable(RequestContextHolder.getRequestAttributes());
        }

        @Override
        public Consumer<Optional<RequestAttributes>> copy() {
            // load requestcontex into real-call thread
            // this method call by resilience4j bulkhead thread;
            return requestAttributes -> requestAttributes.ifPresent(context -> {
                RequestContextHolder.resetRequestAttributes();
                RequestContextHolder.setRequestAttributes(context);
            });
        }

        @Override
        public Consumer<Optional<RequestAttributes>> clear() {
            // clean requestcontext finally ;
            // this method call by resilience4j bulkhead thread;
            return requestAttributes -> RequestContextHolder.resetRequestAttributes();
        }
    }

}

